/* 
 * Copyright (c) 2008 Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using BeIT.MemCached;
using ExtensionLoader;
using ExtensionLoader.Config;
using OpenMetaverse;

namespace AssetServer.Extensions
{
    public class MemcachedStorage : IExtension<AssetServer>, IStorageProvider
    {
        const string EXTENSION_NAME = "MemcachedStorage"; // Used in metrics reporting

        AssetServer server;
        MemcachedClient cacheClient;
        IStorageProvider storageBackend;

        public MemcachedStorage()
        {
        }

        #region Required Interfaces

        public void Start(AssetServer server)
        {
            this.server = server;
            cacheClient = null;

            try
            {
                IConfig memcacheConfig = server.ConfigFile.Configs["Memcached"];

                if (memcacheConfig.Contains("Servers"))
                {
                    string[] servers = memcacheConfig.GetString("Servers").Split(',');

                    try
                    {
                        MemcachedClient.Setup("AssetServer", servers);
                        cacheClient = MemcachedClient.GetInstance("AssetServer");
                    }
                    catch (Exception ex)
                    {
                        Logger.Log.Error("Failed to connect to memcached: " + ex.Message);
                    }
                }
            }
            catch (Exception)
            {
                Logger.Log.Error("Failed to load [Memcached] section from config file " + AssetServer.CONFIG_FILE);
            }

            // Search through the loaded extensions for another IStorageProvider
            // that will serve as the fallback for the cache
            foreach (IExtension<AssetServer> extension in ExtensionLoader<AssetServer>.Extensions)
            {
                if (extension is IStorageProvider && extension != this)
                    storageBackend = extension as IStorageProvider;
            }

            if (storageBackend == null)
                Logger.Log.Warn("Memcached caching storage provider loaded with no fallback storage provider");
        }

        public void Stop()
        {
        }

        public BackendResponse TryFetchMetadata(UUID assetID, out Metadata metadata)
        {
            metadata = null;
            BackendResponse ret;
            string cacheID = assetID.ToString() + "-metadata";

            if (cacheClient != null)
            {
                byte[] data = cacheClient.Get(cacheID) as byte[];
                if (data != null)
                {
                    metadata = new Metadata();
                    metadata.Deserialize(data);
                }
            }

            if (metadata != null)
            {
                ret = BackendResponse.Success;
            }
            else if (storageBackend != null)
            {
                BackendResponse response = storageBackend.TryFetchMetadata(assetID, out metadata);

                if (response == BackendResponse.Success && cacheClient != null)
                {
                    if (cacheClient.Set(cacheID, metadata.SerializeToBytes()))
                        Logger.Log.Debug("Cached " + cacheID);
                }

                ret = response;
            }
            else
            {
                ret = BackendResponse.Failure;
            }

            server.MetricsProvider.LogAssetMetadataFetch(EXTENSION_NAME, ret, assetID, DateTime.Now);
            return ret;
        }

        public BackendResponse TryFetchData(UUID assetID, out byte[] assetData)
        {
            assetData = null;
            BackendResponse ret;
            string cacheID = assetID.ToString();

            if (cacheClient != null)
                assetData = cacheClient.Get(cacheID) as byte[];

            if (assetData != null)
            {
                ret = BackendResponse.Success;
            }
            else if (storageBackend != null)
            {
                BackendResponse response = storageBackend.TryFetchData(assetID, out assetData);

                if (response == BackendResponse.Success && cacheClient != null)
                {
                    if (cacheClient.Set(cacheID, assetData))
                        Logger.Log.Debug("Cached " + cacheID);
                }

                ret = response;
            }
            else
            {
                ret = BackendResponse.Failure;
            }

            server.MetricsProvider.LogAssetDataFetch(EXTENSION_NAME, ret, assetID, (assetData != null ? assetData.Length : 0), DateTime.Now);
            return ret;
        }

        public BackendResponse TryFetchDataMetadata(UUID assetID, out Metadata metadata, out byte[] assetData)
        {
            metadata = null;

            BackendResponse response = TryFetchData(assetID, out assetData);
            if (response == BackendResponse.Success)
                response = TryFetchMetadata(assetID, out metadata);

            return response;
        }

        public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData, out UUID assetID)
        {
            assetID = metadata.ID = UUID.Random();
            return TryCreateAsset(metadata, assetData);
        }

        public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData)
        {
            BackendResponse ret;

            if (storageBackend != null)
            {
                BackendResponse response = storageBackend.TryCreateAsset(metadata, assetData);

                if (response == BackendResponse.Success && cacheClient != null)
                {
                    // There is a high likelihood that an asset that was just uploaded (or its metdata)
                    // will be fetched very soon. Call TryFetchDataMetadata to pre-emptively cache the
                    // final stored data
                    TryFetchDataMetadata(metadata.ID, out metadata, out assetData);
                }

                ret = response;
            }
            else
            {
                ret = BackendResponse.Failure;
            }

            // Don't log the storage to the metrics provider, the actual storage provider will do that
            return ret;
        }

        public int ForEach(Action<Metadata> action, int start, int count)
        {
            // Can't iterate over memcached contents, pass this directly to the backend
            if (storageBackend != null)
                return storageBackend.ForEach(action, start, count);
            else
                return 0;
        }

        #endregion Required Interfaces
    }
}
